
#ifndef RVersion_h
#define RVersion_h

// Author:
//   Miguel de Icaza (miguel@ximian.com)
//
// (C) Ximian, Inc.  http://www.ximian.com
// Copyright (C) 2004, 2006 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

#include "Object.h"
#include "String.h"
#include <System/IComparable.h>
#include <System/Int32.h>
#include <System/Char.h>
#include <System/Generic/IComparable.h>
#include <System/Generic/IEquatable.h>
#include <System/ArgumentException.h>

namespace Riccsson
{
	namespace System
	{
		class Version sealed
			: public Riccsson::System::Object
			, public Riccsson::System::ICloneable
			, public Riccsson::System::IComparable
			, public Riccsson::System::Generic::IComparable<Version>
			, public Riccsson::System::Generic::IEquatable<Version> 
		{
			int _Major, _Minor, _Build, _Revision;

				private: static const int UNDEFINED = -1;


				private: void CheckedSet (int defined, int major, int minor, int build, int revision)
				 {
					 // defined should be 2, 3 or 4


					 if (major < 0) {
						 throw Riccsson::System::ArgumentOutOfRangeException ("major");
					 }
					 this->_Major = major;


					 if (minor < 0) {
						 throw Riccsson::System::ArgumentOutOfRangeException ("minor");
					 }
					 this->_Minor = minor;


					 if (defined == 2) {
						 this->_Build = UNDEFINED;
						 this->_Revision = UNDEFINED;
						 return;
					 }


					 if (build < 0) {
						 throw Riccsson::System::ArgumentOutOfRangeException ("build");
					 }
					 this->_Build = build;


					 if (defined == 3) {
						 this->_Revision = UNDEFINED;
						 return;
					 }


					 if (revision < 0) {
						 throw Riccsson::System::ArgumentOutOfRangeException ("revision");
					 }
					 this->_Revision = revision;
				 }


				public: Version ()
				{
					CheckedSet (2, 0, 0, -1, -1);
				}


				public: Version (string* version)
				{
					int n;
					GC_PTR<Riccsson::System::Array<GC_PTR<string>>> vals;
					int major = -1, minor = -1, build = -1, revision = -1;


					if (version == null) {
						throw Riccsson::System::ArgumentNullException ("version");
					}


					vals = version->Split ('.');
					n = (**vals).Length;


					if (n < 2 || n > 4) {
						throw Riccsson::System::ArgumentException ("There must be 2, 3 or 4 components in the version string.");
					}


					if (n > 0)
						major = Riccsson::System::Int32::Parse ((**vals)[0]);
					if (n > 1)
						minor = Riccsson::System::Int32::Parse ((**vals)[1]);
					if (n > 2)
						build = Riccsson::System::Int32::Parse ((**vals)[2]);
					if (n > 3)
						revision = Riccsson::System::Int32::Parse ((**vals)[3]);


					CheckedSet (n, major, minor, build, revision);
				}


				public: Version (int major, int minor)
				{
					CheckedSet (2, major, minor, 0, 0);
				}


				public: Version (int major, int minor, int build)
				{
					CheckedSet (3, major, minor, build, 0);
				}


				public: Version (int major, int minor, int build, int revision)
				{
					CheckedSet (4, major, minor, build, revision);
				}


				public: PROP3_GET(int, Build)
				{
					return _Build;
				}


				public: PROP3_GET(int, Major)
				{
					return _Major;
				}


				public: PROP3_GET(int, Minor)
				{
					return _Minor;
				}


				public: PROP3_GET(int, Revision)
				{
					return _Revision;
				}
				public: PROP3_GET(short, MajorRevision)
				{
					return (short)(_Revision >> 16);
				}


				public: PROP3_GET(short, MinorRevision)
				{
					return (short)_Revision;
				}


				public: GC_PTR<object> Clone ()
				{
					if (_Build == -1)
						return new Version (_Major, _Minor);
					else if (_Revision == -1)
						return new Version (_Major, _Minor, _Build);
					else
						return new Version (_Major, _Minor, _Build, _Revision);
				}


				public: int CompareTo (object* version)
				{
					if (version == null)
						return 1;

					if (! (instanceof(version, Version)))
						throw Riccsson::System::ArgumentException("Argument to Version.CompareTo must be a Version.");

					return this->CompareTo ((Version*) version);
				}


				public: override bool Equals (object* obj)
				{
					return this->Equals ((Version*)obj);
				}


				public: int CompareTo (Version* value)
				{
					if (value == null)
						return 1;

					if (this->_Major > value->_Major)
						return 1;
					else if (this->_Major < value->_Major)
						return -1;


					if (this->_Minor > value->_Minor)
						return 1;
					else if (this->_Minor < value->_Minor)
						return -1;


					if (this->_Build > value->_Build)
						return 1;
					else if (this->_Build < value->_Build)
						return -1;


					if (this->_Revision > value->_Revision)
						return 1;
					else if (this->_Revision < value->_Revision)
						return -1;


					return 0;
				}


				public: bool Equals (Version* obj)
				{
					return ((obj != null) &&
						(obj->_Major == _Major) &&
						(obj->_Minor == _Minor) &&
						(obj->_Build == _Build) &&
						(obj->_Revision == _Revision));
				}


				public: override int GetHashCode ()
				{
					return (_Revision << 24) | (_Build << 16) | (_Minor << 8) | _Major;
				}


				// <summary>
				//   Returns a stringified representation of the version, format:
				//   major.minor[.build[.revision]]
				// </summary>
				public: override GC_PTR<Riccsson::System::string> ToString ()
				{
					GC_PTR<string> mm = new string(**Riccsson::System::Int32(_Major).ToString() + "." + **Riccsson::System::Int32(_Minor).ToString());

					if (_Build != UNDEFINED)
						mm = new string(**mm + "." + **Riccsson::System::Int32(_Build).ToString());
					if (_Revision != UNDEFINED)
						mm = new string(**mm + "." + **Riccsson::System::Int32(_Revision).ToString());


					return mm;
				}


				// <summary>
				//    LAME: This API is lame, since there is no way of knowing
				//    how many fields a Version object has, it is unfair to throw
				//    an ArgumentException, but this is what the spec claims.
				//
				//    ie, Version a = Version (1, 2);  a.ToString (3) should
				//    throw the expcetion.
				// </summary>
				public: GC_PTR<Riccsson::System::string> ToString (int fieldCount)
				{
					if (fieldCount == 0)
						return new string(String::Empty);
					if (fieldCount == 1)
						return Riccsson::System::Int32(_Major).ToString ();
					if (fieldCount == 2)
						return new string(**Riccsson::System::Int32(_Major).ToString () + "." + **Riccsson::System::Int32(_Minor).ToString ());
					if (fieldCount == 3){
						if (_Build == UNDEFINED)
							throw Riccsson::System::ArgumentException ("fieldCount is larger than the number of components defined in this instance.");
						return new string(**Riccsson::System::Int32(_Major).ToString() + "." + **Riccsson::System::Int32(_Minor).ToString() + "." + **Riccsson::System::Int32(_Build).ToString());
					}
					if (fieldCount == 4){
						if (_Build == UNDEFINED || _Revision == UNDEFINED)
							throw ArgumentException ("fieldCount is larger than the number of components defined in this instance.");
						return new string(**Riccsson::System::Int32(_Major).ToString () + "." + **Riccsson::System::Int32(_Minor).ToString () + "." +
							**Riccsson::System::Int32(_Build).ToString () + "." + **Riccsson::System::Int32(_Revision).ToString ());
					}
					throw Riccsson::System::ArgumentException (string("Invalid fieldCount parameter: ") + **Riccsson::System::Int32(fieldCount).ToString());        
				}

				// Implemented the TryParse separated from the Parse impl due to its
				// small size, and because the Parse one depends on the exceptions thrown by
				// Int32.Parse to match .Net, and detecting such exceptions here is not worth it.
				public: static bool TryParse (string* input, Version out result)
				{
					int n;
					GC_PTR<Riccsson::System::Array<GC_PTR<string>>> vals;
					GC_PTR<Riccsson::System::Array<int>> values; // actual parsed values


					result = null;


					if (input == null)
						return false;


					vals = input->Split ('.');
					n = vals->Length;


					if (n < 2 || n > 4)
						return false;


					values = new Riccsson::System::Array<int>(n);
					for (int i = 0; i < n; i++) {
						int part;
						if (!Int32::TryParse((**vals)[i], /*out*/ part) || part < 0)
							return false;


						values [i] = part;
					}


					result = Version ();
					if (n > 0)
						result._Major = (**values)[0];
					if (n > 1)
						result._Minor = (**values)[1];
					if (n > 2)
						result._Build = (**values)[2];
					if (n > 3)
						result._Revision = (**values)[3];


					return true;
				}


				// a very gentle way to construct a Version object which takes 
				// the first four numbers in a string as the version
				private: static Version CreateFromString (Riccsson::System::string* info)
				 {
					 int major = 0;
					 int minor = 0;
					 int build = 0;
					 int revision = 0;
					 int state = 1;
					 int number = UNDEFINED; // string may not begin with a digit


					 if (info == null)
						 return Version (0, 0, 0, 0);


					 for (int i=0; i < info->Length; i++) {
						 char c = (*info) [i];
						 if (Riccsson::System::Char::IsDigit (c)) {
							 if (number < 0) {
								 number = (c - '0');
							 }
							 else {
								 number = (number * 10) + (c - '0');
							 }
						 }
						 else if (number >= 0) {
							 // assign
							 switch (state) {
							 case 1:
								 major = number;
								 break;
							 case 2:
								 minor = number;
								 break;
							 case 3:
								 build = number;
								 break;
							 case 4:
								 revision = number;
								 break;
							 }
							 number = -1;
							 state ++;
						 }
						 // ignore end of string
						 if (state == 5)
							 break;
					 }


					 // Last number
					 if (number >= 0)
					 {
						 switch (state)
						 {
						 case 1:
							 major = number;
							 break;
						 case 2:
							 minor = number;
							 break;
						 case 3:
							 build = number;
							 break;
						 case 4:
							 revision = number;
							 break;
						 }
					 }
					 return Version (major, minor, build, revision);
				 }
		};

		inline bool operator== (Version v1, Version v2) 
		{
			return v1.Equals(&v2);
		}


		inline bool operator!= (Version v1, Version v2)
		{
			return !v1.Equals(&v2);
		}


		inline bool operator> (Version v1, Version v2)
		{
			return v1.CompareTo (&v2) > 0;
		}


		inline bool operator>= (Version v1, Version v2)
		{
			return v1.CompareTo (&v2) >= 0;
		}


		inline bool operator< (Version v1, Version v2)
		{
			return v1.CompareTo (&v2) < 0;
		}


		inline bool operator<= (Version v1, Version v2)
		{
			return v1.CompareTo (&v2) <= 0;
		}


		inline Version Parse (string* input)
		{
			// Exactly the same as calling Version(string) .ctor
			return Version (input);
		}
	}
}

#endif